<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>SPI | lilei</title>
    <meta name="generator" content="VuePress 1.8.2">
    <link rel="icon" href="/images/icons/logo.png">
    <link rel="apple-touch-icon" href="/images/icons/logo.png">
    <link rel="manifest" href="/manifest.json">
    <script type="text/javascript" src="/assets/js/load.js"></script>
    <meta name="description" content="个人网站">
    <meta name="image" content="https://images.gitbook.cn/b15400e0-69bd-11eb-8f82-1d786eda1f74">
    <meta name="twitter:title" content="SPI">
    <meta name="twitter:description" content="">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:image" content="https://images.gitbook.cn/b15400e0-69bd-11eb-8f82-1d786eda1f74">
    <meta name="twitter:url" content="http://www.lileiblog.xyz/Java/SPI.html">
    <meta property="og:type" content="article">
    <meta property="og:title" content="SPI">
    <meta property="og:description" content="">
    <meta property="og:image" content="https://images.gitbook.cn/b15400e0-69bd-11eb-8f82-1d786eda1f74">
    <meta property="og:url" content="http://www.lileiblog.xyz/Java/SPI.html">
    <meta property="og:site_name" content="lilei">
    <meta itemprop="name" content="SPI">
    <meta itemprop="description" content="">
    <meta itemprop="image" content="https://images.gitbook.cn/b15400e0-69bd-11eb-8f82-1d786eda1f74">
    <meta name="theme-color" content="#3eaf7c">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="msapplication-TileImage" content="/images/icons/logo.png">
    <meta name="msapplication-TileColor" content="#ffffff">
    <meta name="keywords" content="lilei,vue,vuepress,vuepress-theme,theme,主题,vuepress主题,blog
        ,vuepress-blog,java,python,shell,sql,golang,script,shell script,nginx,windows,去广告,androd,google drive
        ,one drive,idea,eclipse,git,小程序">
    <meta name="referrer" content="never">
    
    <link rel="preload" href="/assets/css/0.styles.8932179b.css" as="style"><link rel="preload" href="/assets/js/app.269009d6.js" as="script"><link rel="preload" href="/assets/js/3.974b9720.js" as="script"><link rel="preload" href="/assets/js/31.dc382b56.js" as="script"><link rel="prefetch" href="/assets/js/10.5915e99b.js"><link rel="prefetch" href="/assets/js/11.ff732762.js"><link rel="prefetch" href="/assets/js/12.8bb86d91.js"><link rel="prefetch" href="/assets/js/13.18bde5b0.js"><link rel="prefetch" href="/assets/js/14.314fae34.js"><link rel="prefetch" href="/assets/js/15.0a4ff48f.js"><link rel="prefetch" href="/assets/js/16.ffaf1503.js"><link rel="prefetch" href="/assets/js/17.49a31a6e.js"><link rel="prefetch" href="/assets/js/18.7e6443bf.js"><link rel="prefetch" href="/assets/js/19.6880cc9a.js"><link rel="prefetch" href="/assets/js/20.1fb3f255.js"><link rel="prefetch" href="/assets/js/21.0cd39eea.js"><link rel="prefetch" href="/assets/js/22.392d85cb.js"><link rel="prefetch" href="/assets/js/23.f50adb15.js"><link rel="prefetch" href="/assets/js/24.43e324e1.js"><link rel="prefetch" href="/assets/js/25.e3663205.js"><link rel="prefetch" href="/assets/js/26.da141fb8.js"><link rel="prefetch" href="/assets/js/27.6c05235a.js"><link rel="prefetch" href="/assets/js/28.c124245a.js"><link rel="prefetch" href="/assets/js/29.d185870d.js"><link rel="prefetch" href="/assets/js/30.8587d422.js"><link rel="prefetch" href="/assets/js/32.20ba32e9.js"><link rel="prefetch" href="/assets/js/33.6ecd7de9.js"><link rel="prefetch" href="/assets/js/34.2b91c13a.js"><link rel="prefetch" href="/assets/js/35.66ed0733.js"><link rel="prefetch" href="/assets/js/36.ce7bca84.js"><link rel="prefetch" href="/assets/js/37.d00bb5ac.js"><link rel="prefetch" href="/assets/js/38.c3ecbb6f.js"><link rel="prefetch" href="/assets/js/39.308f4175.js"><link rel="prefetch" href="/assets/js/4.520f6ecc.js"><link rel="prefetch" href="/assets/js/40.f4b98bc2.js"><link rel="prefetch" href="/assets/js/41.95fae64f.js"><link rel="prefetch" href="/assets/js/42.fe4f877b.js"><link rel="prefetch" href="/assets/js/43.ca8f222e.js"><link rel="prefetch" href="/assets/js/44.4d4ee79e.js"><link rel="prefetch" href="/assets/js/45.79178b33.js"><link rel="prefetch" href="/assets/js/46.c3817d0b.js"><link rel="prefetch" href="/assets/js/47.b8252443.js"><link rel="prefetch" href="/assets/js/48.df9d5d3b.js"><link rel="prefetch" href="/assets/js/49.d812fdb2.js"><link rel="prefetch" href="/assets/js/5.f1a24a16.js"><link rel="prefetch" href="/assets/js/50.55cfcaa8.js"><link rel="prefetch" href="/assets/js/51.b8608b02.js"><link rel="prefetch" href="/assets/js/52.52c5e00d.js"><link rel="prefetch" href="/assets/js/53.3a9bd5fb.js"><link rel="prefetch" href="/assets/js/54.a4240c13.js"><link rel="prefetch" href="/assets/js/55.01c4b876.js"><link rel="prefetch" href="/assets/js/56.f29549a3.js"><link rel="prefetch" href="/assets/js/57.01438a04.js"><link rel="prefetch" href="/assets/js/58.29bb4626.js"><link rel="prefetch" href="/assets/js/59.e8b89408.js"><link rel="prefetch" href="/assets/js/6.8c478b39.js"><link rel="prefetch" href="/assets/js/60.bedb5381.js"><link rel="prefetch" href="/assets/js/61.bdfbe584.js"><link rel="prefetch" href="/assets/js/62.cb825d1a.js"><link rel="prefetch" href="/assets/js/63.a9516572.js"><link rel="prefetch" href="/assets/js/64.aa854ed7.js"><link rel="prefetch" href="/assets/js/65.285f883d.js"><link rel="prefetch" href="/assets/js/66.12849f37.js"><link rel="prefetch" href="/assets/js/67.c18b00e4.js"><link rel="prefetch" href="/assets/js/68.fa59daa9.js"><link rel="prefetch" href="/assets/js/69.e9d5dda4.js"><link rel="prefetch" href="/assets/js/7.cfb0d0ea.js"><link rel="prefetch" href="/assets/js/70.db126cce.js"><link rel="prefetch" href="/assets/js/71.6acb04cd.js"><link rel="prefetch" href="/assets/js/72.7ff0ab11.js"><link rel="prefetch" href="/assets/js/73.9a66da79.js"><link rel="prefetch" href="/assets/js/74.0a38a030.js"><link rel="prefetch" href="/assets/js/75.7b32dbb3.js"><link rel="prefetch" href="/assets/js/8.cb9c08e5.js"><link rel="prefetch" href="/assets/js/9.ad652cfe.js"><link rel="prefetch" href="/assets/js/vendors~flowchart.110a1065.js">
    <link rel="stylesheet" href="/assets/css/0.styles.8932179b.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/images/icons/logo.png" alt="lilei" class="logo"> <span class="site-name can-hide">lilei</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/Form/" class="nav-link">
  Form
</a></div><div class="nav-item"><a href="/IDE/" class="nav-link">
  IDE
</a></div><div class="nav-item"><a href="/Java/" class="nav-link router-link-active">
  Java
</a></div><div class="nav-item"><a href="/Other/" class="nav-link">
  Other
</a></div><div class="nav-item"><a href="/Python/" class="nav-link">
  Python
</a></div><div class="nav-item"><a href="/SQL/" class="nav-link">
  SQL
</a></div><div class="nav-item"><a href="/Shell/" class="nav-link">
  Shell
</a></div><div class="nav-item"><a href="/System/" class="nav-link">
  System
</a></div> <a href="https://github.com/woytu/notes-vuepress" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/Form/" class="nav-link">
  Form
</a></div><div class="nav-item"><a href="/IDE/" class="nav-link">
  IDE
</a></div><div class="nav-item"><a href="/Java/" class="nav-link router-link-active">
  Java
</a></div><div class="nav-item"><a href="/Other/" class="nav-link">
  Other
</a></div><div class="nav-item"><a href="/Python/" class="nav-link">
  Python
</a></div><div class="nav-item"><a href="/SQL/" class="nav-link">
  SQL
</a></div><div class="nav-item"><a href="/Shell/" class="nav-link">
  Shell
</a></div><div class="nav-item"><a href="/System/" class="nav-link">
  System
</a></div> <a href="https://github.com/woytu/notes-vuepress" target="_blank" rel="noopener noreferrer" class="repo-link">
    GitHub
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group depth-0"><p class="sidebar-heading open"><span>Java</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/Java/" aria-current="page" class="sidebar-link">Java</a></li><li><a href="/Java/Annotation.html" class="sidebar-link">Annotation</a></li><li><a href="/Java/JDK安装配置.html" class="sidebar-link">JDK安装配置</a></li><li><a href="/Java/JDK工具.html" class="sidebar-link">JDK工具</a></li><li><a href="/Java/Java构建管理.html" class="sidebar-link">Java构建管理</a></li><li><a href="/Java/Java笔记.html" class="sidebar-link">Java笔记</a></li><li><a href="/Java/ORM.html" class="sidebar-link">ORM</a></li><li><a href="/Java/SPI.html" aria-current="page" class="active sidebar-link">SPI</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/Java/SPI.html#flag" class="sidebar-link">Flag</a></li><li class="sidebar-sub-header"><a href="/Java/SPI.html#什么是-spi" class="sidebar-link">什么是 SPI？</a></li><li class="sidebar-sub-header"><a href="/Java/SPI.html#jdk-中的-spi" class="sidebar-link">JDK 中的 SPI</a></li><li class="sidebar-sub-header"><a href="/Java/SPI.html#spi-如何实现代码的解耦" class="sidebar-link">SPI 如何实现代码的解耦？</a></li><li class="sidebar-sub-header"><a href="/Java/SPI.html#spi-适合什么场景下使用" class="sidebar-link">SPI 适合什么场景下使用？</a></li><li class="sidebar-sub-header"><a href="/Java/SPI.html#使用-spi-的优势和劣势在哪里" class="sidebar-link">使用 SPI 的优势和劣势在哪里？</a></li></ul></li><li><a href="/Java/Spring.html" class="sidebar-link">Spring</a></li><li><a href="/Java/Tomcat.html" class="sidebar-link">Tomcat</a></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="spi"><a href="#spi" class="header-anchor">#</a> SPI</h1> <p></p><div class="table-of-contents"><ul><li><a href="#flag">Flag</a></li><li><a href="#什么是-spi">什么是 SPI？</a></li><li><a href="#jdk-中的-spi">JDK 中的 SPI</a><ul><li><a href="#实例以及测试">实例以及测试</a></li><li><a href="#源码分析">源码分析</a></li></ul></li><li><a href="#spi-如何实现代码的解耦">SPI 如何实现代码的解耦？</a></li><li><a href="#spi-适合什么场景下使用">SPI 适合什么场景下使用？</a></li><li><a href="#使用-spi-的优势和劣势在哪里">使用 SPI 的优势和劣势在哪里？</a></li></ul></div><p></p> <h2 id="flag"><a href="#flag" class="header-anchor">#</a> Flag</h2> <ul><li><a href="https://blog.csdn.net/shang_xs/article/details/86560469" target="_blank" rel="noopener noreferrer">从Java SPI机制实现到Dubbo SPI扩展<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/shang_xs/article/details/86560691" target="_blank" rel="noopener noreferrer">从Java SPI机制实现到Spring Boot SPI扩展<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://www.jianshu.com/p/25b3559ecc47" target="_blank" rel="noopener noreferrer">AVA SPI机制详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://www.jianshu.com/p/3a3edbcd8f24" target="_blank" rel="noopener noreferrer">深入理解SPI机制<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul> <ul><li>OSGI（Open Service Gateway Initiative），是一个由OSGi Alliance发起的以Java为技术平台的动态模块化规范</li> <li>Jigsaw Java9 modules</li> <li>观察者设计模式 <a href="https://blog.csdn.net/wanggang514260663/article/details/86138144" target="_blank" rel="noopener noreferrer">Java设计模式之观察者模式<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li>Spring 实现<code>ApplicationContextAware</code>接口获取到指定接口的所有实现</li></ul> <ul><li><a href="https://github.com/Enaium/BullPlugin" target="_blank" rel="noopener noreferrer">https://github.com/Enaium/BullPlugin<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li>springboot插件式开发框架 <a href="https://gitee.com/starblues/springboot-plugin-framework-parents" target="_blank" rel="noopener noreferrer">https://gitee.com/starblues/springboot-plugin-framework-parent<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul> <h2 id="什么是-spi"><a href="#什么是-spi" class="header-anchor">#</a> 什么是 SPI？</h2> <p>SPI 全称为（Service Provider Interface），字面意思为服务提供者接口，是<strong>JDK 内置的一种服务提供发现机制</strong>。
这一机制为很多框架的扩展提供了可能，比如在 Dubbo、JDBC、Spring Boot 中都使用到了 SPI 机制。
说白了就是提供给“服务提供厂商”或者“插件开发者”使用的接口</p> <p>SPI 是一种动态发现替换机制，例如我们在学习 Java Web 的时候连接数据库使用的 java.sql.Driver 接口，可以根据不同的驱动，
连接不同的数据库，如常用的 MySQL 或者 Oracle 数据库,，我们在使用 JDBC 连接数据库的时候首先需要的就是连接驱动：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token class-name">Class</span><span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span><span class="token string">&quot;com.mysql.jdbc.Driver&quot;</span><span class="token punctuation">)</span>
</code></pre></div><p>加载 MySQL 驱动后，就会 执行其中的静态代码，把 Driver 注册到 DriverManager 中那么通过数据库的 url、用户名、密码，
我们就可以成功连接到你的 MySQL 数据库并可以进行相应的操作，如果你要更换成 Oracle 数据库，那么就需要更换对应的驱动，
下面是 JDBC 连接数据库的一个步骤，帮助大家回忆：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token comment">//声明数据库驱动，数据源的 url，用于登录数据库的账户和密码（将其他功能封装成方法的时候方便使用）</span>
<span class="token class-name">String</span> driver <span class="token operator">=</span> <span class="token string">&quot;数据库驱动名称&quot;</span>；
<span class="token class-name">String</span> url <span class="token operator">=</span> <span class="token string">&quot;数据库连接地址&quot;</span><span class="token class-name">String</span> user <span class="token operator">=</span> <span class="token string">&quot;用来连接数据库的用户名&quot;</span>；
<span class="token class-name">String</span> pwd <span class="token operator">=</span> <span class="token string">&quot;用来连接数据库的密码&quot;</span>；
<span class="token comment">//加载数据库驱动  </span>
<span class="token class-name">Class</span><span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span>driver<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//根据 url 创建数据库连接对象 Connection </span>
<span class="token class-name">Connection</span> con <span class="token operator">=</span> <span class="token class-name">DriverManager</span><span class="token punctuation">.</span><span class="token function">getConnection</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span>user<span class="token punctuation">,</span>pwd<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//用数据库连接对象创建 Statement 对象(或 PrepareStatement)</span>
<span class="token class-name">Statement</span> s <span class="token operator">=</span> con<span class="token punctuation">.</span><span class="token function">createStatement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//或</span>
<span class="token class-name">PrepareStatement</span> ps <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">con<span class="token punctuation">.</span></span>PrepareStatement</span><span class="token punctuation">(</span>sql<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//做数据库的增删改查工作</span>
<span class="token class-name">ResultSet</span> rs <span class="token operator">=</span> ps<span class="token punctuation">.</span><span class="token function">executeQuery</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//关闭结果集对象 Resultset，statement 对象，connection 对象，</span>
rs<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
s<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
con<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">//各个步骤的异常处理</span>
</code></pre></div><p>结合上面的代码和下面的图片来简单分析一下。</p> <p>我们在使用 MySQL 的数据库时，需要导入一个 MySQL 的连接驱动包，打开这个驱动包，你会发现在下图的目录中有一个文件，
<code>Class.forName(driver)</code> 它会去找到这个 com.mysql.jdbc.Driver 的类，然后用 DriverManager 加载这个类，
然后再去使用这个类中的方法，例如 <code>con.PrepareStatement(sql);</code> 就是使用的 com.mysql.jdbc.Driver 这个类中的方法，
同理如果你将驱动换成 Oracle，那么 DriverManager 就会得到 Oracle 的连接对象，那么 <code>con.PrepareStatement(sql);</code>
调用的就是 Oracle 对应驱动中的方法，也就是说，如果我们将数据库换成 Orale，理论上，上面的操作数据库的代码是不需要变动的，
只需要更换驱动、url 和账号密码，这部分我们后面都是以配置文件的形式写入，所以很好的将代码和数据库解耦了。</p> <p>下图是我在网上找到图片：</p> <p><img src="https://images.gitbook.cn/b15400e0-69bd-11eb-8f82-1d786eda1f74" alt="来源于博客员作者：架构之路"></p> <p>如果你还是不能很好的理解，没有关系，接下来，我们就慢慢剖析这个 SPI。</p> <h2 id="jdk-中的-spi"><a href="#jdk-中的-spi" class="header-anchor">#</a> JDK 中的 SPI</h2> <h3 id="实例以及测试"><a href="#实例以及测试" class="header-anchor">#</a> 实例以及测试</h3> <p>我们先从 JDK 开始，通过一个很简单的例子来看下它是怎么用的。</p> <p>这是例子的代码结构：</p> <p><img src="https://images.gitbook.cn/5d9e7d50-6b47-11eb-b964-590e861bb8fd" alt="在这里插入图片描述"></p> <p>首先，我们需要定义一个接口 SPIService。</p> <p>这个接口只有一个打印的方法：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">public</span> interfaceSPIService <span class="token punctuation">{</span>
    <span class="token function">voidprint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>然后我们再定义一个实现类，只做打印输出：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">public</span> classSPIServiceImplimplementsSPIService<span class="token punctuation">{</span>
    <span class="token function">publicvoidprint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">&quot;print..............&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>然后我们需要在 resources 下创建文件夹：META-INF/services
然后在 services 文件夹下创建文件，文件名就是服务接口的全限定类名：</p> <p><img src="https://images.gitbook.cn/de1d1820-6b4b-11eb-a8aa-992450a0658d" alt="在这里插入图片描述"></p> <p>文件的内容就是该接口的实现类的全限定类名。</p> <p>文件内容：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token class-name"><span class="token namespace">com<span class="token punctuation">.</span>spi<span class="token punctuation">.</span>service<span class="token punctuation">.</span>impl<span class="token punctuation">.</span></span>SPIServiceImpl</span>
</code></pre></div><p>然后我们就可以通过 ServiceLoader.load 方法拿到实现类的实例，并调用它的方法。</p> <p>我们在启动类中测试：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">package</span> <span class="token namespace">com<span class="token punctuation">.</span>spi</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token namespace">com<span class="token punctuation">.</span>spi<span class="token punctuation">.</span>service<span class="token punctuation">.</span></span><span class="token class-name">SPIService</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Iterator</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">ServiceLoader</span><span class="token punctuation">;</span>

publicclassSPIApplication<span class="token punctuation">{</span>
    <span class="token function">publicstaticvoidmain</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token comment">//加载类</span>
        <span class="token class-name">ServiceLoader</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SPIService</span><span class="token punctuation">&gt;</span></span> load <span class="token operator">=</span> <span class="token class-name">ServiceLoader</span><span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token class-name">SPIService</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SPIService</span><span class="token punctuation">&gt;</span></span> iterator <span class="token operator">=</span> load<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            <span class="token comment">//获取类的实例</span>
            <span class="token class-name">SPIService</span> service <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            service<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>运行结果：</p> <p><img src="https://images.gitbook.cn/ae4b5070-6b4c-11eb-9226-31bcf945eb52" alt="在这里插入图片描述"></p> <h3 id="源码分析"><a href="#源码分析" class="header-anchor">#</a> 源码分析</h3> <p>首先，我们先来了解下 ServiceLoader，看看它的类结构：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">public</span> finalclassServiceLoader<span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span> implementsIterable<span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>
    <span class="token comment">//配置文件的路径privatestaticfinal String PREFIX = &quot;META-INF/services/&quot;;</span>
    <span class="token comment">//加载的服务类或接口privatefinal Class&lt;S&gt; service;</span>
    <span class="token comment">//已加载的服务类集合private LinkedHashMap&lt;String,S&gt; providers = new LinkedHashMap&lt;&gt;();</span>
    <span class="token comment">//类加载器privatefinal ClassLoader loader;</span>
    <span class="token comment">//内部类，真正加载服务类private LazyIterator lookupIterator;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当我们调用 load 方法时，并没有真正的去加载和查找服务类。而是调用了 ServiceLoader 的构造方法，
在这里最重要的是实例化了内部类 LazyIterator，ServiceLoader 才是接下来的主角：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">ServiceLoader</span><span class="token punctuation">(</span><span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span> svc<span class="token punctuation">,</span> <span class="token class-name">ClassLoadercl</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">//要加载的接口</span>
    service <span class="token operator">=</span> <span class="token class-name">Objects</span><span class="token punctuation">.</span><span class="token function">requireNonNull</span><span class="token punctuation">(</span>svc<span class="token punctuation">,</span> <span class="token string">&quot;Service interface cannot be null&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//类加载器</span>
    loader <span class="token operator">=</span> <span class="token punctuation">(</span>cl <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token class-name">ClassLoader</span><span class="token punctuation">.</span><span class="token function">getSystemClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> cl<span class="token punctuation">;</span>
    <span class="token comment">//访问控制器</span>
    acc <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">getSecurityManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token class-name">AccessController</span><span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token comment">//先清空</span>
    providers<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//实例化内部类 </span>
    <span class="token class-name">LazyIterator</span> lookupIterator <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LazyIterator</span><span class="token punctuation">(</span>service<span class="token punctuation">,</span> loader<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>查找实现类和创建实现类的过程，都在 LazyIterator 完成。当我们调用 iterator.hasNext 和 iterator.next 方法的时候，
实际上调用的都是 LazyIterator 的相应方法：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">public</span> <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span> <span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

    returnnew <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

        <span class="token function">publicbooleanhasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            <span class="token keyword">return</span> lookupIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">public</span> <span class="token class-name">S</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            <span class="token keyword">return</span> lookupIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>因此，我们重点关注 <code>lookupIterator.hasNext()</code> 方法，它最终会调用到 <code>hasNextService</code>，在这里返回实现类名称：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">private</span> classLazyIteratorimplementsIterator<span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>
    <span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">S</span><span class="token punctuation">&gt;</span></span> service<span class="token punctuation">;</span>
    <span class="token class-name">ClassLoader</span> loader<span class="token punctuation">;</span>
    <span class="token class-name">Enumeration</span><span class="token generics"><span class="token punctuation">&lt;</span>URL<span class="token punctuation">&gt;</span></span> configs <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">&gt;</span></span> pending <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token class-name">String</span> nextName <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    
    <span class="token function">privatebooleanhasNextService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token comment">// 第二次调用的时候，已经解析完成了，直接返回</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>nextName <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            returntrue<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>configs <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// META-INF/services/ 加上接口的全限定类名，就是文件服务类的文件</span>
            <span class="token comment">// META-INF/services/com.viewscenes.netsupervisor.spi.SPIService</span>
            <span class="token class-name">String</span> fullName <span class="token operator">=</span> PREFIX <span class="token operator">+</span> service<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//将文件路径转成 URL 对象</span>
            configs <span class="token operator">=</span> loader<span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span>fullName<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>pending <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>pending<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">//解析 URL 文件对象，读取内容，最后返回</span>
            pending <span class="token operator">=</span> <span class="token function">parse</span><span class="token punctuation">(</span>service<span class="token punctuation">,</span> configs<span class="token punctuation">.</span><span class="token function">nextElement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">//拿到第一个实现类的类名</span>
        nextName <span class="token operator">=</span> pending<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        returntrue<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>然后当我们调用 <code>next()</code> 方法的时候，调用到 <code>lookupIterator.nextService</code>，它通过反射的方式，创建实现类的实例并返回：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token keyword">private</span> <span class="token class-name">S</span> <span class="token function">nextService</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">//全限定类名</span>
    <span class="token class-name">String</span> cn <span class="token operator">=</span> nextName<span class="token punctuation">;</span>
    nextName <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token comment">//创建类的 Class 对象</span>
    <span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token operator">?</span><span class="token punctuation">&gt;</span></span> c <span class="token operator">=</span> <span class="token class-name">Class</span><span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span>cn<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> loader<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//通过 newInstance 实例化</span>
    <span class="token class-name">S</span> p <span class="token operator">=</span> service<span class="token punctuation">.</span><span class="token function">cast</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//放入集合，返回实例</span>
    providers<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>cn<span class="token punctuation">,</span> p<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> p<span class="token punctuation">;</span> 
<span class="token punctuation">}</span>
</code></pre></div><p>到这为止，已经获取到了类的实例，这就是 SPI 机制的一个内部原理。</p> <h2 id="spi-如何实现代码的解耦"><a href="#spi-如何实现代码的解耦" class="header-anchor">#</a> SPI 如何实现代码的解耦？</h2> <p>其实在前面提到 JDBC 的时候已经大致了解了 SPI 解耦，那么我们就再结合前面的实例，用通俗的语言概述一下吧。</p> <p>我们可以通过下面这个简单的流程图来进一步理解 SPI 是如何解耦和扩展的。</p> <p>首先需要定义一个标准化的服务接口，例如上面的实例 SPIService，里面有一些服务例如 print，然后实现这个接口，
我们暂定它为实现类 A（SPIServiceImpl），它实现的 print 方法里面可以自定义实现内容，如果现在要求说要再增加一种打印的方式，
但是保留之前的 print 的打印方式，那么我们就可以再定义一个实现类 B 去实现这个标准化的服务接口，如果后面再增加新的打印方式也一样，
直接加，或者某一种不需要了，直接去掉即可。</p> <p>那么由此可见，它是将标准的服务接口与服务提供方实现类进行解耦的，我们可以思考一下，它进行扩展是非常方便的，只需要实现标准服务接口，
然后在 <code>META-INF/services</code> 对应的文件中添加该实现类的全限定类名，然后在实现类的方法中填充实现就可以了。</p> <p>但如果你要是想修改标准服务接口的方法定义，那么就比较麻烦了，只要是实现这个接口的类都需要改，也就是软件设计原则提到的<strong>开闭原则</strong>，
因此我们需要一开始就设计好标准服务接口的内容，保证软件系统的稳定性和延续性。</p> <p><img src="https://images.gitbook.cn/dd2c5030-7185-11eb-b964-590e861bb8fd" alt="在这里插入图片描述"></p> <h2 id="spi-适合什么场景下使用"><a href="#spi-适合什么场景下使用" class="header-anchor">#</a> SPI 适合什么场景下使用？</h2> <p>比较常见的例子：</p> <ol><li>数据库驱动加载接口实现类的加载：JDBC 加载不同类型数据库的驱动。</li> <li>日志门面接口实现类加载：SLF4J 加载不同提供商的日志实现类。</li> <li>Spring：Spring 中大量使用了 SPI，比如：对 servlet3.0 规范对 ServletContainerInitializer 的实现、
自动类型转换 Type Conversion SPI（Converter SPI、Formatter SPI）等。</li> <li>Spring Boot 的自动配置：Spring Boot 的 Web 应用都能正常运行，Spring Boot 正是依靠自动配置来完成，
自动配置就是依靠 SpringFactoriesLoader 来加载的。</li> <li>Dubbo：Dubbo 中也大量使用 SPI 的方式实现框架的扩展，不过它对 Java 提供的原生 SPI 做了封装，允许用户扩展实现 Filter 接口。</li></ol> <p>概括地说，适用于：<strong>调用者根据实际使用需要，启用、扩展、或者替换方案（框架）的实现策略</strong>。</p> <p>很多地方写的是替换框架，但是我在这里改成了方案，更方便理解（如果这里有一些争议的话，欢迎大家在评论区留言），
实际上我们可以把每一个实现类都叫做一种方案，例如我们前面提到的 SPIService 的实现类 A 和 B，就是对 print 这个方法的两种方案，
SLF4J 加载不同提供商的日志实现类实际上也是加载不同的日志方案，因此实际上如果我们想在自己的项目中运用到 SPI，
那么它最好的使用就是对某一个事件不同方案的处理，例如给公司员工计算月/年薪资（里面包含了绩效、KPI、考勤、奖金等等），
公司针对不同的层级或者区域的员工有不同的方案，这个例子的事件就是薪资计算，不管你什么类型什么等级的员工，
计算薪资的总和都是这几项加起来的结果，不同的是每一项的根据不同的实现方案计算内容有一定的差别。</p> <h2 id="使用-spi-的优势和劣势在哪里"><a href="#使用-spi-的优势和劣势在哪里" class="header-anchor">#</a> 使用 SPI 的优势和劣势在哪里？</h2> <p><strong>优点：</strong></p> <p>不用多说，优势就是实现解耦，使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离，而不是耦合在一起。
应用程序可以根据实际业务情况启用框架扩展或替换框架组件，或者调整不同的方案。<strong>满足软件设计的开闭原则</strong>。</p> <p><strong>缺点：</strong></p> <ol><li><p>虽然 ServiceLoader 也算是使用的延迟加载，<strong>但是基本只能通过遍历全部获取</strong>，也就是接口的实现类全部加载并实例化一遍，
如果你并不想用某些实现类，它也被加载并实例化了，这就造成了浪费。</p></li> <li><p><strong>获取某个实现类的方式不够灵活</strong>，只能通过 Iterator 形式获取，不能根据某个参数来获取对应的实现类。</p></li></ol> <p>一般我们在实际项目开发的时候，会使用枚举来确定不同方案，然后每个方案都有一个 getSchemeId 的方法用于返回这个方案对应的 ID，
通过这个 id 去筛选当前需要的方案。</p> <p>小例子：</p> <div class="language-java extra-class"><pre class="language-java"><code><span class="token comment">//动态加载 SPIService 的所有方案实现类</span>
<span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SPIService</span><span class="token punctuation">&gt;</span></span> services <span class="token operator">=</span> <span class="token class-name">SpiServiceLoader</span><span class="token punctuation">.</span><span class="token function">getService</span><span class="token punctuation">(</span><span class="token class-name">SPIService</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">SPIService</span> service<span class="token operator">:</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">//获取当前这个人对应方案的实现类</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token class-name">Objects</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>service<span class="token punctuation">.</span><span class="token function">getSchemeId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> person<span class="token punctuation">.</span><span class="token function">getSchemeId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//使用这个方案的实现类</span>
        service<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">break</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>这个里面 SpiServiceLoader 是自己封装的 SPI 加载器，相当于把前面实例中的 main 中的 ServiceLoader 和 Iterator 封装起来了。</p> <ol start="3"><li><strong>多个并发多线程使用 ServiceLoader 类的实例是不安全的。</strong> 实际上这个可以解决，我们在第 2 点的基础上修改。</li></ol> <p>自己封装的 SpiServiceLoader 在使用 getService 中会调用 ServiceLoader，那么我们给 getService 方法加上锁 <code>synchronized</code> 就可以解决并发的问题了。</p> <p>这里至于为什么没有放 SpiServiceLoader，是因为这个代码是公司的代码，不能随便用来写文章，所以，这里提供了思路，实现就没有那么难了。</p> <p>以上就是我对 SPI 机制的理解和总结，实际上真实的项目中远比这个要复杂。</p></div> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/woytu/notes-vuepress/edit/master//Java/SPI.md" target="_blank" rel="noopener noreferrer">在 GitHub 上编辑此页</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <div class="last-updated"><span class="prefix">上次更新:</span> <span class="time">2021-03-21 12:03:13</span></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
      ←
      <a href="/Java/ORM.html" class="prev">
        ORM
      </a></span> <span class="next"><a href="/Java/Spring.html">
        Spring
      </a>
      →
    </span></p></div> </main></div><div class="global-ui"><!----></div></div>
    <script src="/assets/js/app.269009d6.js" defer></script><script src="/assets/js/3.974b9720.js" defer></script><script src="/assets/js/31.dc382b56.js" defer></script>
  </body>
</html>
